package com.atom.core;

import android.util.Log;

import com.atom.annotation.ApiImplNonNull;
import com.atom.annotation.ApiImplNullable;
import com.atom.annotation.api.ApiCLassFilter;
import com.atom.annotation.api.ApiImplCallback;
import com.atom.annotation.api.ApiImplContext;
import com.atom.annotation.api.ApiImplProvider;
import com.atom.annotation.bean.ApiImplWrapper;
import com.atom.annotation.bean.ApiImpls;

import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

public class ApiImplRouter implements ApiImplContext {

    private static final String TAG = "ApiImplRouter";

    /**
     * 是否为debug模式
     */
    private static boolean isDebug = false;

    public static void setDebug(boolean isDebug) {
        ApiImplRouter.isDebug = isDebug;
    }

    /**
     * 注册的ApiImpl集合
     */
    private static final Set<Class<? extends ApiImpls>> registerClass = new HashSet<>();

    /**
     * 单例
     */
    private static class SingletonHolder {
        private static final ApiImplRouter instance = new ApiImplRouter();
    }

    public static ApiImplRouter getInstance() {
        return SingletonHolder.instance;
    }

    /**
     * plugin拦截并插入的方法
     */
    private static void loadProxyClass() {

    }

    private static void registerClass(String className) {
        if (isDebug) {
            Log.i(TAG, String.format("func:registerClass:%s", className));
        }
        if (!isEmpty(className)) {
            try {
                Class<?> clazz = Class.forName(className);
                if (ApiImpls.class.isAssignableFrom(clazz)) {
                    registerClass.add((Class<? extends ApiImpls>) clazz);
                }
            } catch (Exception e) {
                if (isDebug) {
                    Log.e(TAG, String.format("func:registerClass:error:%s", e.getLocalizedMessage()));
                }
                e.printStackTrace();
            }
        }
    }

    private final Map<String, WeakReference<Object>> mCaches = new HashMap<>();
    private final Map<String, Object> mSingletonBeans = new HashMap<>();
    private final Map<String, Class<?>> mCacheClasses = new HashMap<>();
    private final Map<String, Boolean> mEnabledImpls = new HashMap<>();
    private final Map<Class<?>, ApiImplProvider> mCacheImplProvider = new HashMap<>();
    private final List<ApiImpls> mApiImpls = new LinkedList<>();
    private long mCachesLastCheckTime = System.currentTimeMillis();

    private ApiImplRouter() {
        loadProxyClass();
        loadPackages();
    }

    private void loadPackages() {
        for (Class<? extends ApiImpls> clazz : registerClass) {
            try {
                if (isDebug) {
                    Log.i(TAG, String.format("func:loadPackages:%s", clazz.getCanonicalName()));
                }
                mApiImpls.add(clazz.newInstance());
            } catch (Exception e) {
                if (isDebug) {
                    Log.e(TAG, String.format("func:loadPackages:error:%s", e.getLocalizedMessage()));
                }
                e.printStackTrace();
            }
        }
    }

    @Override
    public <T> Collection<Class<? extends T>> getApis(@ApiImplNonNull Class<T> requiredType) {
        if (isDebug) {
            Log.i(TAG, String.format("func:getApis:%s", requiredType.getCanonicalName()));
        }
        List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> entryList = filterApiImpls(requiredType);
        List<Class<? extends T>> classes = new ArrayList<>();
        for (Map.Entry<Class<? extends T>, ApiImplWrapper<T>> entry : entryList
        ) {
            classes.add(entry.getKey());
        }
        return Collections.unmodifiableCollection(classes);
    }

    @Override
    public <T> void getApis(@ApiImplNonNull Class<T> requiredType, @ApiImplNonNull ApiImplCallback<Collection<Class<? extends T>>> callback) {
        if (callback != null) {
            Collection<Class<? extends T>> apis = getApis(requiredType);
            if (apis != null && !apis.isEmpty()) {
                callback.call(this, apis);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getApis:requestClass[%s] , result is null or empty", requiredType.getSimpleName()));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getApis:requestClass[%s] , callback != null", requiredType.getSimpleName()));
            }
        }
    }

    @Override
    public <T> Collection<Class<? extends T>> getApis(@ApiImplNonNull Class<T> requiredType, String name, boolean useRegex) {
        List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> entryList = filterApiImpls(requiredType);
        if (!isEmpty(name)) {
            entryList = filterApiImplsByNameAndRegex(name, entryList, useRegex);
        }
        List<Class<? extends T>> classes = new ArrayList<>();
        for (Map.Entry<Class<? extends T>, ApiImplWrapper<T>> entry : entryList
        ) {
            classes.add(entry.getKey());
        }
        return Collections.unmodifiableCollection(classes);
    }

    @Override
    public <T> void getApis(Class<T> requiredType, String name, boolean useRegex, ApiImplCallback<Collection<Class<? extends T>>> callback) {
        if (callback != null) {
            Collection<Class<? extends T>> apis = getApis(requiredType, name, useRegex);
            if (apis != null && !apis.isEmpty()) {
                callback.call(this, apis);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getApis:requestClass[%s],name=[%s],useRegex=[%s] , result is null or empty", requiredType.getSimpleName(), name, useRegex));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getApis:requestClass[%s],name=[%s],useRegex=[%s] , callback != null", requiredType.getSimpleName(), name, useRegex));
            }
        }
    }

    @Override
    public <T> Collection<Class<? extends T>> getApis(@ApiImplNonNull Class<T> requiredType, @ApiImplNonNull ApiCLassFilter<T> filter) {
        List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> entryList = filterApiImpls(requiredType);
        List<Class<? extends T>> classes = new ArrayList<>();
        for (Map.Entry<Class<? extends T>, ApiImplWrapper<T>> entry : entryList
        ) {
            if (filter.accept(this, entry.getKey(), entry.getValue())) {
                classes.add(entry.getKey());
            }
        }
        return Collections.unmodifiableCollection(classes);
    }

    @Override
    public <T> void getApis(Class<T> requiredType, ApiCLassFilter<T> filter, ApiImplCallback<Collection<Class<? extends T>>> callback) {
        if (callback != null) {
            Collection<Class<? extends T>> apis = getApis(requiredType, filter);
            if (apis != null && !apis.isEmpty()) {
                callback.call(this, apis);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getApis:requestClass[%s] by filter , result is null or empty", requiredType.getSimpleName()));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getApis:requestClass[%s] by filter , callback != null", requiredType.getSimpleName()));
            }
        }
    }

    @Override
    public <T> Class<? extends T> getApi(@ApiImplNonNull Class<T> requiredType) {
        return getApi(requiredType, null, 0, false);
    }

    @Override
    public <T> void getApi(Class<T> requiredType, ApiImplCallback<Class<? extends T>> callback) {
        if (callback != null) {
            Class<? extends T> api = getApi(requiredType);
            if (api != null) {
                callback.call(this, api);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getApi:requestClass[%s] , result is null", requiredType.getSimpleName()));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getApi:requestClass[%s] , callback != null", requiredType.getSimpleName()));
            }
        }
    }

    @Override
    public <T> Class<? extends T> getApi(Class<T> requiredType, String name) {
        return getApi(requiredType, name, 0, false);
    }

    @Override
    public <T> void getApi(Class<T> requiredType, String name, ApiImplCallback<Class<? extends T>> callback) {
        if (callback != null) {
            Class<? extends T> api = getApi(requiredType, name);
            if (api != null) {
                callback.call(this, api);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getApi:requestClass[%s],name=[%s] , result is null", requiredType.getSimpleName(), name));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getApi:requestClass[%s],name=[%s] , callback != null", requiredType.getSimpleName(), name));
            }
        }
    }

    @Override
    public <T> Class<? extends T> getApi(@ApiImplNonNull Class<T> requiredType, long version) {
        return getApi(requiredType, null, version, false);
    }

    @Override
    public <T> void getApi(Class<T> requiredType, long version, ApiImplCallback<Class<? extends T>> callback) {
        if (callback != null) {
            Class<? extends T> api = getApi(requiredType, version);
            if (api != null) {
                callback.call(this, api);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getApi:requestClass[%s],version=[%s] , result is null", requiredType.getSimpleName(), version));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getApi:requestClass[%s],version=[%s] , callback != null", requiredType.getSimpleName(), version));
            }
        }
    }

    @Override
    public <T> Class<? extends T> getApi(@ApiImplNonNull Class<T> requiredType, String name, long version, boolean useRegex) {
        return findApiImpl(requiredType, name, version, useRegex);
    }

    @Override
    public <T> void getApi(Class<T> requiredType, String name, long version, boolean useRegex, ApiImplCallback<Class<? extends T>> callback) {
        if (callback != null) {
            Class<? extends T> api = getApi(requiredType, name, version, useRegex);
            if (api != null) {
                callback.call(this, api);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getApi:requestClass[%s],name=[%s],version=[%s],useRegex=[%s] , result is null", requiredType.getSimpleName(), name, version, useRegex));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getApi:requestClass[%s],name=[%s],version=[%s],useRegex=[%s] , callback != null", requiredType.getSimpleName(), name, version, useRegex));
            }
        }
    }

    @Override
    public <T> void setImpl(@ApiImplNonNull Class<T> requiredType, String name, long version, T entity) {
        String key = convertKey(requiredType, name, version);
        synchronized (mSingletonBeans) {
            if (!mSingletonBeans.containsKey(key)) {
                mSingletonBeans.put(key, entity);
            }
        }
    }

    @Override
    public <T> T getImpl(@ApiImplNonNull Class<T> requiredType) {
        return getImpl(requiredType, null, 0, false);
    }

    @Override
    public <T> void getImpl(Class<T> requiredType, ApiImplCallback<T> callback) {
        if (callback != null) {
            T impl = getImpl(requiredType);
            if (impl != null) {
                callback.call(this, impl);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getImpl:requestClass[%s] , result is null", requiredType.getSimpleName()));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getImpl:requestClass[%s] , callback != null", requiredType.getSimpleName()));
            }
        }
    }

    @Override
    public <T> T getImpl(@ApiImplNonNull Class<T> requiredType, String name) {
        return getImpl(requiredType, name, 0, false);
    }

    @Override
    public <T> void getImpl(Class<T> requiredType, String name, ApiImplCallback<T> callback) {
        if (callback != null) {
            T impl = getImpl(requiredType, name);
            if (impl != null) {
                callback.call(this, impl);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getImpl:requestClass[%s],name=[%s] , result is null", requiredType.getSimpleName(), name));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getImpl:requestClass[%s],name=[%s] , callback != null", requiredType.getSimpleName(), name));
            }
        }
    }

    @Override
    public <T> T getImpl(@ApiImplNonNull Class<T> requiredType, long version) {
        return getImpl(requiredType, null, version, false);
    }

    @Override
    public <T> void getImpl(Class<T> requiredType, long version, ApiImplCallback<T> callback) {
        if (callback != null) {
            T impl = getImpl(requiredType, version);
            if (impl != null) {
                callback.call(this, impl);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getImpl:requestClass[%s],version=[%s] , result is null", requiredType.getSimpleName(), version));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getImpl:requestClass[%s],version=[%s] , callback != null", requiredType.getSimpleName(), version));
            }
        }
    }

    @Override
    public <T> T getImpl(@ApiImplNonNull Class<T> requiredType, String name, long version, boolean useRegex) {
        T impl = hasApi(requiredType, name, version);
        if (impl != null) return impl;
        Class<? extends T> implClass = findApiImpl(requiredType, name, version, useRegex);
        if (implClass == null) {
            if (!Modifier.isAbstract(requiredType.getModifiers())) {
                implClass = requiredType;
            }
        }
        impl = createApiImpl(implClass);
        synchronized (mSingletonBeans) {
            mSingletonBeans.put(convertKey(requiredType, name, version), impl);
        }
        return impl;
    }


    @Override
    public <T> void getImpl(Class<T> requiredType, String name, long version, boolean useRegex, ApiImplCallback<T> callback) {
        if (callback != null) {
            T impl = getImpl(requiredType, name, version, useRegex);
            if (impl != null) {
                callback.call(this, impl);
            } else {
                if (isDebug) {
                    Log.i(TAG, String.format("func:getImpl:requestClass[%s],name=[%s],version=[%s],useRegex=[%s] , result is null", requiredType.getSimpleName(), name, version, useRegex));
                }
            }
        } else {
            if (isDebug) {
                Log.i(TAG, String.format("func:getImpl:requestClass[%s],name=[%s],version=[%s],useRegex=[%s] , callback != null", requiredType.getSimpleName(), name, version, useRegex));
            }
        }
    }

    @Override
    public <T> T hasApi(@ApiImplNonNull Class<T> requiredType, String name, long version) {
        String key = convertKey(requiredType, name, version);
        synchronized (mSingletonBeans) {
            if (mSingletonBeans.containsKey(key)) {
                //noinspection unchecked
                return (T) mSingletonBeans.get(key);
            }
        }
        return null;
    }

    @Override
    public <T> T hasApi(@ApiImplNonNull Class<T> requiredType) {
        T t = hasApi(requiredType, null, 0);
        if (t != null) {
            return t;
        }
        String key = convertKey(requiredType, null, 0);
        synchronized (mSingletonBeans) {
            for (Object obj : mSingletonBeans.values()) {
                if (requiredType.isInstance(obj)) {
                    //noinspection unchecked
                    t = (T) obj;
                    mSingletonBeans.put(key, t);
                    return t;
                }
            }
        }
        return null;
    }

    @Override
    public <T> T newApi(@ApiImplNonNull Class<T> api) {
        return newApi(api, null);
    }

    @Override
    public <T> T newApi(@ApiImplNonNull Class<T> api, String name) {
        return newApi(api, name, 0);
    }

    @Override
    public <T> T newApi(@ApiImplNonNull Class<T> api, String name, long version) {
        Class<? extends T> impl;
        if (Modifier.isAbstract(api.getModifiers())) {
            impl = findApiImpl(api, name, version, false);
        } else {
            impl = api;
        }
        return createApiImpl(impl);
    }


    private String convertKey(Class<?> type, String name, long version) {
        String key = type.getSimpleName();
        if (isEmpty(name) || key.equalsIgnoreCase(name)) {
            return key + "$$" + version;
        }
        return key + "$" + name + "$" + version;
    }


    private <T> T createApiImpl(Class<? extends T> impl) {
        if (impl == null) {
            return null;
        }
        if (isDebug) {
            Log.i(TAG, String.format("func:createApiImpl:implClass[%s]", impl.getSimpleName()));
        }
        T obj = null;
        for (ApiImpls apiImpl : mApiImpls) {
            Class<? extends ApiImplProvider> apiImplProviderClass = apiImpl.getProvider(impl);
            if (apiImplProviderClass == null) continue;
            ApiImplProvider cacheProviderImpl = mCacheImplProvider.get(apiImplProviderClass);
            if (cacheProviderImpl == null) {
                try {
                    cacheProviderImpl = apiImplProviderClass.newInstance();
                    mCacheImplProvider.put(apiImplProviderClass, cacheProviderImpl);
                } catch (Exception ex) {
                    if (isDebug) {
                        Log.e(TAG, String.format("func:createApiImplWithProvider:error:%s", ex.getLocalizedMessage()));
                    }
                }
                if (cacheProviderImpl != null) {
                    try {
                        obj = cacheProviderImpl.create(this, impl);
                        break;
                    } catch (Exception ex) {
                        if (isDebug) {
                            Log.e(TAG, String.format("func:createApiImplWithProvider:error:%s", ex.getLocalizedMessage()));
                        }
                    }
                }
            }
        }

        if (obj == null) {
            try {
                obj = impl.newInstance();
            } catch (Exception ex) {
                if (isDebug) {
                    Log.e(TAG, String.format("func:createApiImpl:error:%s", ex.getLocalizedMessage()));
                }
            }
        }

        return obj;
    }


    private <T> Class<? extends T> findApiImpl(Class<T> apiClass, String name, long version, boolean useRegex) {
        if (!isEmpty(name)) {
            final Boolean implEnabled = getImplEnabled(name);
            if (implEnabled != null && !implEnabled) {
                return null;
            }
        }
        final String keyClass = convertKey(apiClass, name, version);
        final Class<?> aCacheClass;
        synchronized (mCacheClasses) {
            aCacheClass = mCacheClasses.get(keyClass);
        }
        if (aCacheClass != null && apiClass.isAssignableFrom(aCacheClass)) {
            return (Class<? extends T>) aCacheClass;
        }
        List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> imps = filterApiImpls(apiClass);
        if (!isEmpty(name)) {
            imps = filterApiImplsByNameAndRegex(name, imps, useRegex);
        }
        final Class<? extends T> aClass = filterApiImplByVersion(version, imps);

        synchronized (mCacheClasses) {
            mCacheClasses.put(keyClass, aClass);
        }
        return aClass;
    }

    private <T> List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> filterApiImplsByNameAndRegex(String name, List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> apiImps, boolean useRegex) {
        List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> temp = new ArrayList<>();
        for (Map.Entry<Class<? extends T>, ApiImplWrapper<T>> imp : apiImps) {
            if (imp.getValue() == null) continue;
            if (!useRegex && name.equals(imp.getValue().name)) {
                temp.add(imp);
            } else if (useRegex && Pattern.matches(name, imp.getValue().name)) {
                temp.add(imp);
            }
        }
        return temp;
    }

    private <T> List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> filterApiImpls(Class<T> requiredType) {
        List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> apiImpls = new LinkedList<>();
        String name;
        Boolean enabled;
        for (ApiImpls imp : mApiImpls) {
            Map<Class<? extends T>, ApiImplWrapper<T>> apiImplsMap = imp.getApiImpls(requiredType);
            if (apiImplsMap != null) {
                for (Map.Entry<Class<? extends T>, ApiImplWrapper<T>> entry : apiImplsMap.entrySet()) {
                    ApiImplWrapper<T> value = entry.getValue();
                    if (value != null) {
                        name = value.name;
                        if (!name.isEmpty()) {
                            enabled = getImplEnabled(name);
                            if (enabled != null && !enabled) {
                                continue;
                            }
                        }
                    }
                    apiImpls.add(entry);
                }
            }
        }
        return apiImpls;
    }

    private <T> Class<? extends T> filterApiImplByVersion(long version, List<Map.Entry<Class<? extends T>, ApiImplWrapper<T>>> apiImps) {
        if (version >= 0) {
            for (Map.Entry<Class<? extends T>, ApiImplWrapper<T>> entry : apiImps
            ) {
                if (entry.getValue() != null && entry.getValue().version == version) {
                    return entry.getKey();
                }
            }
        } else {
            int size = apiImps.size();
            Collections.sort(apiImps, (o1, o2) -> (int) (o1.getValue().version - o2.getValue().version));
            int idx = size + (int) version;
            if (idx < 0) {
                idx = 0;
            }
            return apiImps.get(idx).getKey();
        }
        return null;
    }


    @ApiImplNonNull
    @Override
    public String cachePut(@ApiImplNonNull Object data) {
        if (System.currentTimeMillis() - mCachesLastCheckTime > 24 * 3600 * 1000L) {
            mCachesLastCheckTime = System.currentTimeMillis();
            cacheGC();
        }
        long l = System.currentTimeMillis();
        String key = String.valueOf(l);
        synchronized (mCaches) {
            while (mCaches.containsKey(key)) {
                key = String.valueOf(l++);
            }
            mCaches.put(key, new WeakReference<>(data));
        }
        return key;
    }

    protected void cacheGC() {
        synchronized (mCaches) {
            Iterator<Map.Entry<String, WeakReference<Object>>> iterator = mCaches.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, WeakReference<Object>> entry = iterator.next();
                if (entry != null) {
                    WeakReference<Object> value = entry.getValue();
                    if (value == null || value.get() == null) {
                        iterator.remove();
                        if (isDebug) {
                            Log.i(TAG, String.format("func:cacheGC:%s", entry.getKey()));
                        }
                    }
                }
            }
        }
    }

    @ApiImplNullable
    @Override
    public Object cacheGet(@ApiImplNonNull String key) {
        WeakReference<Object> weakReference;
        synchronized (mCaches) {
            weakReference = mCaches.get(key);
        }
        if (weakReference == null) {
            return null;
        }
        return weakReference.get();
    }

    @ApiImplNullable
    @Override
    public Object cacheRemove(@ApiImplNonNull String key) {
        synchronized (mCaches) {
            return mCaches.remove(key);
        }
    }

    @Override
    public void setImplEnabled(@ApiImplNonNull String name, Boolean enable) {
        synchronized (mEnabledImpls) {
            mEnabledImpls.put(name.trim(), enable);
        }
    }

    @Override
    public Boolean getImplEnabled(@ApiImplNonNull String name) {
        synchronized (mEnabledImpls) {
            return mEnabledImpls.get(name.trim());
        }
    }


    private static boolean isEmpty(CharSequence str) {
        return str == null || str.length() == 0;
    }

}
